﻿using EnvDTE;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using VSLangProj;
using VSIXUtilities.EventsListeners;
using VSIXUtilities.DataObjects.Enums;
using VSIXUtilities.Helpers;
using Microsoft.VisualStudio.Shell.Design;
using System.Diagnostics;
using System.ComponentModel;

//http://dotneteers.net/blogs/divedeeper/archive/2008/10/16/LearnVSXNowPart36.aspx

namespace VSIXUtilities.DataObjects
{
    [DebuggerDisplay("ItemId = {ItemId}, Name = {Name}, Caption = {Caption}, CanonicalName = {CanonicalName}")]
    public class IVsHierarchyWrapper : IComparable<IVsHierarchyWrapper>, IVsFileChangeEventsSubscriber
	{
		#region Fields
		Guid hierGuid = typeof(IVsHierarchy).GUID;
		List<IVsHierarchyWrapper> ListIVsHierarchyChildrenChildren;
		IVsHierarchyWrapper ParentInstance;
		string stringFilePath;
		bool boolNotifyFileChanges;
		VsFileChangeEvents VsFileChangeEventsInstance;
		#endregion

		#region Properties

		public IVsHierarchy AccurateHierarchy
		{
			get
			{
				uint itemId = 0;
				IVsHierarchy tempHierarchy = null;
				if(BrowseObject != null && BrowseObject is IVsBrowseObject)
				{
					((IVsBrowseObject)BrowseObject).GetProjectItem(out tempHierarchy, out itemId);
					return tempHierarchy;
				}
				return Hierarchy;
			}
		}
		public string[] AggregateProjectTypeGuids
		{
			get
			{
				string[] Result = new string[] { };
				if(Hierarchy is IVsAggregatableProject)
				{
					string pbstrProjTypeGuids;
					((IVsAggregatableProject)Hierarchy).GetAggregateProjectTypeGuids(out pbstrProjTypeGuids);
					Result = pbstrProjTypeGuids.Split(';');
				}
				return Result;
			}
		}
		public Bitmap Bitmap
		{
			get
			{
				return Bitmap.FromHicon(IconHandle);
			}
		}
		public object BrowseObject
		{
			get
			{
				return GetProperty(__VSHPROPID.VSHPROPID_BrowseObject);
			}
		}
		public string CanonicalName
		{
			get
			{
				string pvar;
				Hierarchy.GetCanonicalName(ItemId, out pvar);
				return pvar;
			}
		}
		public string Caption
		{
			get
			{
				string Result = GetProperty<string>(__VSHPROPID.VSHPROPID_Caption);
				return string.IsNullOrEmpty(Result) ? Name : Result;
			}
		}
		public List<IVsHierarchyWrapper> Children
		{
			get
			{
				if(ListIVsHierarchyChildrenChildren == null)
				{
					ListIVsHierarchyChildrenChildren = new List<IVsHierarchyWrapper>();
					EnumHierarchyItems(Hierarchy, ItemId, 0, false);
				}
				//if(IsRoot)
				{
					ListIVsHierarchyChildrenChildren.Sort();
				}
				return ListIVsHierarchyChildrenChildren;
			}
		}
		public object ExtObject
		{
			get
			{
				return GetProperty(__VSHPROPID.VSHPROPID_ExtObject);
			}
		}
		public IVsHierarchyWrapper FirstChild
		{
			get
			{
				IVsHierarchyWrapper Result = null;
				uint FirstChildId = GetItemId(GetProperty(__VSHPROPID.VSHPROPID_FirstChild));
				if(FirstChildId != VSConstants.VSITEMID_NIL)
				{
					Result = new IVsHierarchyWrapper(Hierarchy, FirstChildId, this);
				}
				return Result;
			}
		}
		public IVsHierarchy Hierarchy
		{
			get;
			private set;
		}
		public Icon Icon
		{
			get
			{
				return Icon.FromHandle(IconHandle);
			}
		}
		public IntPtr IconHandle
		{
			get
			{
				IntPtr Result = new IntPtr(GetProperty<int>(__VSHPROPID.VSHPROPID_IconHandle));
				if(Result == IntPtr.Zero)
				{
					if(IconIndex == -1)
					{
						Result = Sharp.Utilities.FileSystemHelper.GetTypeIconHandle(Name, false);
					}
					else if(IconImgList != IntPtr.Zero)
					{
						Result = SafeNativeMethods.ImageList_GetIcon(IconImgList, IconIndex, FStyle.ILD_NORMAL);
					}
				}
				else
				{
					Icon IconTest = Icon.FromHandle(Result);
					if(IconTest.Width > 16)
					{
						Result = SafeNativeMethods.ImageList_GetIcon(IconImgList, IconIndex, FStyle.ILD_NORMAL);
					}
				}
				return Result;
			}
		}
		public IntPtr IconImgList
		{
			get
			{
				return new IntPtr(GetProperty<int>(__VSHPROPID.VSHPROPID_IconImgList));
			}
		}
		public int IconIndex
		{
			get
			{
				return GetProperty<int>(__VSHPROPID.VSHPROPID_IconIndex);
			}
		}
		public bool IsFolder
		{
			get
			{
				return IsProject && ToProject().Kind == EnvDTE80.ProjectKinds.vsProjectKindSolutionFolder;
			}
		}
		public bool IsHiddenItem
		{
			get
			{
				return GetProperty<bool>(__VSHPROPID.VSHPROPID_IsHiddenItem);
			}
		}
        public bool IsSolution
        {
            get
            {
                return AccurateHierarchy is IVsSolution || AccurateHierarchy is IVsSolution3 || AccurateHierarchy is IVsSolution4;
            }
        }
        public bool IsProject
        {
            get
            {
                return ExtObject is Project;
            }
        }
        public bool IsProjectItem
		{
			get
			{
				return ExtObject is ProjectItem;
			}
		}
		public bool IsReference
		{
			get
			{
				return !IsRoot && Parent.IsReferencesFolder;
			}
		}
		public bool IsReferencesFolder
		{
			get
			{
				return Caption == "References" && TypeGuid.ToString("B") == EnvDTE.Constants.vsProjectItemKindVirtualFolder.ToLower();
			}
		}
		public bool IsRoot
		{
			get
			{
				return ItemId == VSConstants.VSITEMID_ROOT;
			}
		}
		public uint ItemId
		{
			get;
			private set;
		}
		public string Name
		{
			get
			{
				return GetProperty<string>(__VSHPROPID.VSHPROPID_Name);
			}
		}
		public Bitmap OpenFolderBitmap
		{
			get
			{
				return Bitmap.FromHicon(OpenFolderIconHandle);
			}
		}
		public Icon OpenFolderIcon
		{
			get
			{
				return Icon.FromHandle(OpenFolderIconHandle);
			}
		}
		public IntPtr OpenFolderIconHandle
		{
			get
			{
				IntPtr Result = new IntPtr(GetProperty<int>(__VSHPROPID.VSHPROPID_OpenFolderIconHandle));
				if(Result == IntPtr.Zero)
				{
					if(IconIndex == -1)
					{
						Result = Sharp.Utilities.FileSystemHelper.GetTypeIconHandle(Name, false);
					}
					else if(IconImgList != IntPtr.Zero)
					{
						Result = SafeNativeMethods.ImageList_GetIcon(IconImgList, OpenFolderIconIndex, FStyle.ILD_NORMAL);
					}
				}
				else
				{
					Icon IconTest = Icon.FromHandle(Result);
					if(IconTest.Width > 16)
					{
						Result = SafeNativeMethods.ImageList_GetIcon(IconImgList, OpenFolderIconIndex, FStyle.ILD_NORMAL);
					}
				}
				return Result;
			}
		}
		public int OpenFolderIconIndex
		{
			get
			{
				return GetProperty<int>(__VSHPROPID.VSHPROPID_OpenFolderIconIndex);
			}
		}
		public int OverlayIconIndex
		{
			get
			{
				return GetProperty<int>(__VSHPROPID.VSHPROPID_OverlayIconIndex);
			}
		}
		public IVsHierarchyWrapper Parent
		{
			get
			{
				if(ParentInstance == null && ParentId != VSConstants.VSITEMID_NIL)
				{
					ParentInstance = new IVsHierarchyWrapper(Hierarchy, ParentId);
				}
				return ParentInstance;
			}
			private set
			{
				ParentInstance = value;
			}
		}
		public uint ParentId
		{
			get
			{
				object pvar = GetProperty(__VSHPROPID.VSHPROPID_Parent);
				if(pvar is int)
				{
					return (uint)(int)pvar;
				}
				if(pvar is uint)
				{
					return (uint)pvar;
				}
				return (uint)VSConstants.VSITEMID.Nil;
			}
		}
		public Guid ProjectIDGuid
		{
			get
			{
				Guid pvar;
				Hierarchy.GetGuidProperty(ItemId, Convert.ToInt32(__VSHPROPID.VSHPROPID_ProjectIDGuid), out pvar);
				return pvar;
			}
		}
		public string ProjectType
		{
			get
			{
				return GetProperty<string>(__VSHPROPID.VSHPROPID_ProjectType);
			}
		}
		public int StateIconIndex
		{
			get
			{
				return GetProperty<int>(__VSHPROPID.VSHPROPID_StateIconIndex);
			}
		}
		public Guid TypeGuid
		{
			get
			{
				Guid pvar;
				Hierarchy.GetGuidProperty(ItemId, Convert.ToInt32(__VSHPROPID.VSHPROPID_TypeGuid), out pvar);
				return pvar;
			}
		}
		public string TypeName
		{
			get
			{
				return GetProperty<string>(__VSHPROPID.VSHPROPID_TypeName);
			}
		}
		public List<Type> Types
		{
			get
			{
				List<Type> Result = new List<Type>();
				DynamicTypeService typeService = (DynamicTypeService)ServiceProvider.GlobalProvider.GetService(typeof(DynamicTypeService));
				if(typeService != null)
				{
					ITypeDiscoveryService discovery = typeService.GetTypeDiscoveryService(AccurateHierarchy);
					foreach(Type type in discovery.GetTypes(typeof(object), true))
					{
						Result.Add(type);
					}
				}
				return Result;
			}
		}
		public VsFileChangeEvents SubscribtionList
		{
			get
			{
				return VsFileChangeEventsInstance;
			}
			private set
			{
				if(value != VsFileChangeEventsInstance)
				{
					if(VsFileChangeEventsInstance != null && NotifyFileChanges)
					{
						VsFileChangeEventsInstance.Unsubscribe(this);
					}
					VsFileChangeEventsInstance = value;
					if(NotifyFileChanges)
					{
						VsFileChangeEventsInstance.Subscribe(this, _VSFILECHANGEFLAGS.VSFILECHG_Time);
					}
				}
			}
		}
		public bool NotifyFileChanges
		{
			get
			{
				return boolNotifyFileChanges;
			}
			set
			{
				boolNotifyFileChanges = value;
				if(SubscribtionList == null)
				{
					//throw InvalidOperationException();
				}
				else
				{
					if(value)
					{
						SubscribtionList.Subscribe(this, _VSFILECHANGEFLAGS.VSFILECHG_Time);
					}
					else
					{
						SubscribtionList.Unsubscribe(this);
					}
				}
			}
		}
        [Bindable(true)]
        public string LogicalPath
		{
			get
			{
				return (IsRoot)? Caption : Parent.LogicalPath + "\\" + Caption;
			}
		}

		#endregion

		#region Constructors
		/// <summary>
		/// Initializes an instance of the <see cref="IVsHierarchyWrapper"/> class.
		/// </summary>
		/// <param name="Hierarchy"></param>
		/// <param name="ItemId"></param>
		public IVsHierarchyWrapper(IVsHierarchy Hierarchy, uint ItemId)
		{
			this.Hierarchy = Hierarchy;
			this.ItemId = ItemId;
		}
		private IVsHierarchyWrapper(IVsHierarchy Hierarchy, uint ItemId, IVsHierarchyWrapper Parent) : this(Hierarchy, ItemId)
		{
			this.Parent = Parent;
		}
		#endregion

		#region Helper Functions

		/// <summary>
		/// Enumerates over the hierarchy items for the given hierarchy traversing into nested hierarchies.
		/// </summary>
		/// <param name="hierarchy">hierarchy to enmerate over.</param>
		/// <param name="itemid">item id of the hierarchy</param>
		/// <param name="recursionLevel">Depth of recursion. e.g. if recursion started with the Solution
		/// node, then : Level 0 -- Solution node, Level 1 -- children of Solution, etc.</param>
		/// <param name="hierIsSolution">true if hierarchy is Solution Node. This is needed to special
		/// case the children of the solution to work around a bug with VSHPROPID_FirstChild and 
		/// VSHPROPID_NextSibling implementation of the Solution.</param>
		/// <param name="visibleNodesOnly">true if only nodes visible in the Solution Explorer should
		/// be traversed. false if all project items should be traversed.</param>
		/// <param name="processNodeFunc">pointer to function that should be processed on each
		/// node as it is visited in the depth first enumeration.</param>
		private void EnumHierarchyItems(IVsHierarchy hierarchy, uint itemid, int recursionLevel, bool visibleNodesOnly)
		{
			int hr;
			IntPtr nestedHierarchyObj;
			uint nestedItemId;

			// Check first if this node has a nested hierarchy. If so, then there really are two 
			// identities for this node: 1. hierarchy/itemid 2. nestedHierarchy/nestedItemId.
			// We will recurse and call EnumHierarchyItems which will display this node using
			// the inner nestedHierarchy/nestedItemId identity.
			hr = hierarchy.GetNestedHierarchy(itemid, ref hierGuid, out nestedHierarchyObj, out nestedItemId);
			if(VSConstants.S_OK == hr && IntPtr.Zero != nestedHierarchyObj)
			{
				IVsHierarchy nestedHierarchy = Marshal.GetObjectForIUnknown(nestedHierarchyObj) as IVsHierarchy;
				Marshal.Release(nestedHierarchyObj);    // we are responsible to release the refcount on the out IntPtr parameter
				if(nestedHierarchy != null)
				{
					// Display name and type of the node in the Output Window
					EnumHierarchyItems(nestedHierarchy, nestedItemId, recursionLevel, visibleNodesOnly);
				}
			}
			else
			{
				object pVar;

				recursionLevel++;

				//Get the first child node of the current hierarchy being walked
				// NOTE: to work around a bug with the Solution implementation of VSHPROPID_FirstChild,
				// we keep track of the recursion level. If we are asking for the first child under
				// the Solution, we use VSHPROPID_FirstVisibleChild instead of _FirstChild. 
				// In VS 2005 and earlier, the Solution improperly enumerates all nested projects
				// in the Solution (at any depth) as if they are immediate children of the Solution.
				// Its implementation _FirstVisibleChild is correct however, and given that there is
				// not a feature to hide a SolutionFolder or a Project, thus _FirstVisibleChild is 
				// expected to return the identical results as _FirstChild.
				hr = hierarchy.GetProperty(itemid, ((visibleNodesOnly || (IsRoot && recursionLevel == 1) ? (int)__VSHPROPID.VSHPROPID_FirstVisibleChild : (int)__VSHPROPID.VSHPROPID_FirstChild)), out pVar);

				//Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(hr);
				if(VSConstants.S_OK == hr)
				{
					//We are using Depth first search so at each level we recurse to check if the node has any children
					// and then look for siblings.
					uint childId = GetItemId(pVar);
					while(childId != VSConstants.VSITEMID_NIL)
					{
						//EnumHierarchyItems(hierarchy, childId, recursionLevel, false, visibleNodesOnly);
						ListIVsHierarchyChildrenChildren.Add(new IVsHierarchyWrapper(hierarchy, childId, this));

						// NOTE: to work around a bug with the Solution implementation of VSHPROPID_NextSibling,
						// we keep track of the recursion level. If we are asking for the next sibling under
						// the Solution, we use VSHPROPID_NextVisibleSibling instead of _NextSibling. 
						// In VS 2005 and earlier, the Solution improperly enumerates all nested projects
						// in the Solution (at any depth) as if they are immediate children of the Solution.
						// Its implementation   _NextVisibleSibling is correct however, and given that there is
						// not a feature to hide a SolutionFolder or a Project, thus _NextVisibleSibling is 
						// expected to return the identical results as _NextSibling.
						hr = hierarchy.GetProperty(childId, ((visibleNodesOnly || (IsRoot && recursionLevel == 1)) ? (int)__VSHPROPID.VSHPROPID_NextVisibleSibling : (int)__VSHPROPID.VSHPROPID_NextSibling), out pVar);
						if(VSConstants.S_OK == hr)
						{
							childId = GetItemId(pVar);
						}
						else
						{
							//Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(hr);
							break;
						}
					}
				}
			}
		}
		/// <summary>
		/// Gets the item id.
		/// </summary>
		/// <param name="pvar">VARIANT holding an itemid.</param>
		/// <returns>Item Id of the concerned node</returns>
		private static uint GetItemId(object pvar)
		{
			if(pvar == null) return VSConstants.VSITEMID_NIL;
			if(pvar is int) return (uint)(int)pvar;
			if(pvar is uint) return (uint)pvar;
			if(pvar is short) return (uint)(short)pvar;
			if(pvar is ushort) return (uint)(ushort)pvar;
			if(pvar is long) return (uint)(long)pvar;
			return VSConstants.VSITEMID_NIL;
		}
		private object GetProperty(__VSHPROPID PropertyId)
		{
			object pvar;
			Hierarchy.GetProperty(ItemId, Convert.ToInt32(PropertyId), out pvar);
			return pvar;
		}
		private T GetProperty<T>(__VSHPROPID PropertyId)
		{
			object Result = GetProperty(PropertyId);
			return (Result == null) ? default(T) : (T)Result;
		}

		#endregion

		#region Public Functions

		public void Remove()
		{
			if(IsProjectItem)
			{
				ToProjectItem().Remove();
			}
			else if(ExtObject is Reference)
			{
				ToReference().Remove();
			}
		}
		public Project ToProject()
		{
			if(IsProject)
			{
				return (Project)ExtObject;
			}
			else
			{
				return null;
			}
		}
		public IVsProject ToIVsProject()
		{
			return (IVsProject)AccurateHierarchy;
		}
		public IVsSolution ToIVsSolution()
		{
            if (IsSolution)
            {
                return (IVsSolution)AccurateHierarchy;
            }
            else
            {
                return null;
            }
		}
		public ProjectItem ToProjectItem()
		{
			if(IsProjectItem)
			{
				return (ProjectItem)ExtObject;
			}
			else
			{
				return null;
			}
		}
		public Reference ToReference()
		{
			if(ExtObject is Reference)
			{
				return (Reference)ExtObject;
			}
			else if(ExtObject is VSLangProj2.Reference2)
			{
				VSLangProj2.Reference2 r2 = (VSLangProj2.Reference2)ExtObject;
			}
			else if(ExtObject is VSLangProj80.Reference3)
			{
				VSLangProj80.Reference3 r3 = (VSLangProj80.Reference3)ExtObject;
			}
			else
			{
				return null;
			}
			return null;
		}

		#endregion

		#region IComparable<IVsHierarchyWrapper> Members

		public int CompareTo(IVsHierarchyWrapper other)
		{
			if(other == null)
			{
				return -1;
			}
			if(ProjectType != other.ProjectType)
			{
				return String.Compare(ProjectType, other.ProjectType);
			}
			if(IsFolder != other.IsFolder)
			{
				return Convert.ToInt16(other.IsFolder) - Convert.ToInt16(IsFolder);
			}
			return Caption.CompareTo(other.Caption);
		}

		#endregion

		#region IVsFileChangeEventsSubscriber Members

		public string FilePath
		{
			get
			{
				if(string.IsNullOrEmpty(stringFilePath))
				{
					if(IsRoot)
					{
						string pbstrSolutionDirectory;
						string pbstrSolutionFile;
						string pbstrUserOptsFile;
						ToIVsSolution().GetSolutionInfo(out pbstrSolutionDirectory, out pbstrSolutionFile, out pbstrUserOptsFile);
						stringFilePath = pbstrSolutionFile;
					}
					else if(IsProject)
					{
						stringFilePath = ToProject().FullName;
					}
					else
					{
						IVsHierarchyWrapper IVsHierarchyWrapperProject = Parent;
						while(!IVsHierarchyWrapperProject.IsProject)
						{
							IVsHierarchyWrapperProject = IVsHierarchyWrapperProject.Parent;
						}
						string pbstrMkDocument;
						IVsHierarchyWrapperProject.ToIVsProject().GetMkDocument(ItemId, out pbstrMkDocument);
						stringFilePath = pbstrMkDocument;
					}
					if(IsReference && string.IsNullOrEmpty(stringFilePath))
					{
						Reference ReferenceInternal = ToReference();//IVsSolution ivs;ivs.GetItemInfoOfProjref
						if(ReferenceInternal != null)
						{
							stringFilePath = ReferenceInternal.Path;
						}
					}
				}
				return stringFilePath;
			}
		}

		public uint pdwCookie
		{
			get;
			set;
		}

		public int FileChanged(uint cChanges, uint rggrfChange)
		{
			return VSConstants.S_OK;
		}

		#endregion
	}
}